package com.tools.web.work.captcha;

import com.tools.common.object.Note;
import com.tools.common.object.json.JsonKit;
import com.tools.common.thread.TimePair;
import com.tools.common.thread.Timeunit;
import org.springframework.data.redis.connection.RedisConnection;
import org.springframework.data.redis.connection.ReturnType;
import org.springframework.data.redis.core.RedisTemplate;

import java.util.Map;
import java.util.Objects;

/**
 * 使用 RedisTemplate 客户端操作 Redis 实现 CacheService
 * */
@Note("使用 RedisTemplate 客户端操作 Redis 实现 CacheService")
public final class RedisTemplateCaptchaService extends AbsCaptchaService implements CaptchaRedisLuaScript{

    @Note("Spring 操作 Redis 的客户端 RedisTemplate 实例")
    private final RedisTemplate<String, String> redisTemplate;

    /* ********************************************************************************************
     *
     *          构造器
     *
     * *********************************************************************************************
     * */

    public RedisTemplateCaptchaService(RedisTemplate<String, String> redisTemplate) {
        this.redisTemplate = Objects.requireNonNull(redisTemplate);
    }

    /* ********************************************************************************************
     *
     *          工具方法
     *
     * *********************************************************************************************
     * */

    public RedisTemplate<String, String> getRedisTemplate() {
        return redisTemplate;
    }

    @Override
    public int checkEqualsCaptcha(String key, String sourceCode, boolean ignoreCase) {
        Integer result = this.redisTemplate.execute((RedisConnection connection) -> connection.eval(
                CHECK_EQUALS.getBytes(),
                ReturnType.INTEGER,
                1,
                key.getBytes(),
                sourceCode.getBytes(),
                String.valueOf(ignoreCase).getBytes()
        ));
        return result != null ? result : 0;
    }

    @Override
    public int checkEqualsCaptcha(String key, String sourceCode) {
        return this.checkEqualsCaptcha(key, sourceCode, true);
    }

    @Override
    public int checkSlideCaptcha(String key, SlideCaptcha source) {
        Integer result = this.redisTemplate.execute((RedisConnection connection) -> connection.eval(
                CHECK_SLIDE.getBytes(),
                ReturnType.INTEGER,
                1,
                key.getBytes(),
                String.valueOf(source.getBlockX()).getBytes(),
                String.valueOf(source.getBlockY()).getBytes()
        ));
        return result != null ? result : 0;
    }

    @Override
    public int checkClickCaptcha(String key, ClickCaptcha source) {
        String charsJson = JsonKit.toJSON(source.getChars());
        Integer result = this.redisTemplate.execute((RedisConnection connection) -> connection.eval(
                CHECK_CLICK.getBytes(),
                ReturnType.INTEGER,
                1,
                key.getBytes(),
                charsJson.getBytes()
        ));
        return result != null ? result : 0;
    }


    @Override
    public void setEqualsCaptcha(String key, CaptchaResult captchaResult, long expiration, Timeunit timeunit) {
        long expirationSecond = Timeunit.toSecond(expiration, timeunit);
        this.redisTemplate.execute((RedisConnection connection) -> connection.eval(
                SET_EQ_AND_CLICK_CAPTCHA.getBytes(),
                ReturnType.INTEGER,
                1,
                key.getBytes(),
                String.valueOf(expirationSecond).getBytes(),
                captchaResult.asTextIfIs().getBytes(),
                String.valueOf(captchaResult.getAllow()).getBytes(),
                String.valueOf(captchaResult.getIncorrect()).getBytes()
        ));
    }

    @Override
    public void setEqualsCaptcha(String key, CaptchaResult captchaResult, TimePair expiration) {
        this.setEqualsCaptcha(key, captchaResult, expiration.getSourceTime(), expiration.getSourceTimeunit());
    }

    @Override
    public void setSlideCaptcha(String key, CaptchaResult captchaResult, long expiration, Timeunit timeunit) {
        long expirationSecond = Timeunit.toSecond(expiration, timeunit);
        SlideCaptcha slide = (SlideCaptcha) captchaResult.getCaptcha();
        this.redisTemplate.execute((RedisConnection connection) -> connection.eval(
                SET_SLIDE_CAPTCHA.getBytes(),
                ReturnType.INTEGER,
                1,
                key.getBytes(),
                String.valueOf(expirationSecond).getBytes(),
                String.valueOf(slide.getBlockX()).getBytes(),
                String.valueOf(slide.getBlockY()).getBytes(),
                String.valueOf(slide.getFaultTolerant()).getBytes(),
                String.valueOf(captchaResult.getAllow()).getBytes(),
                String.valueOf(captchaResult.getIncorrect()).getBytes()
        ));
    }

    @Override
    public void setSlideCaptcha(String key, CaptchaResult captchaResult, TimePair expiration) {
        this.setSlideCaptcha(key, captchaResult, expiration.getSourceTime(), expiration.getSourceTimeunit());
    }

    @Override
    public void setClickSelectCaptcha(String key, CaptchaResult captchaResult, long expiration, Timeunit timeunit) {
        ClickCaptcha clickCaptcha = (ClickCaptcha) captchaResult.getCaptcha();
        String captcha = JsonKit.toJSON(clickCaptcha.getChars());
        long expirationSecond = Timeunit.toSecond(expiration, timeunit);
        this.redisTemplate.execute((RedisConnection connection) -> connection.eval(
                SET_EQ_AND_CLICK_CAPTCHA.getBytes(),
                ReturnType.INTEGER,
                1,
                key.getBytes(),
                String.valueOf(expirationSecond).getBytes(),
                String.valueOf(captcha).getBytes(),
                String.valueOf(captchaResult.getAllow()).getBytes(),
                String.valueOf(captchaResult.getIncorrect()).getBytes()
        ));
    }

    @Override
    public void setClickSelectCaptcha(String key, CaptchaResult captchaResult, TimePair expiration) {
        this.setClickSelectCaptcha(key, captchaResult, expiration.getSourceTime(), expiration.getSourceTimeunit());
    }

    @Override
    public CaptchaResult getEqualsCaptcha(String key) {
        Map<Object, Object> map = this.redisTemplate.opsForHash().entries(key);
        String captcha = map.get("captcha").toString();
        int incorrect = Integer.parseInt(map.get("incorrect").toString());
        int allow = Integer.parseInt(map.get("allow").toString());
        CaptchaResult captchaResult = new CaptchaResult(captcha, allow);
        captchaResult.setIncorrect(incorrect);
        return captchaResult;
    }

    @Override
    public CaptchaResult getSlideCaptcha(String key) {
        Map<Object, Object> map = this.redisTemplate.opsForHash().entries(key);
        int faultTolerant = Integer.parseInt(map.get("faultTolerant").toString());
        int blockX = Integer.parseInt(map.get("blockX").toString());
        int blockY = Integer.parseInt(map.get("blockY").toString());
        int incorrect = Integer.parseInt(map.get("incorrect").toString());
        int allow = Integer.parseInt(map.get("allow").toString());
        SlideCaptcha slideCaptcha = new SlideCaptcha(blockX, blockY, faultTolerant);
        CaptchaResult captchaResult = new CaptchaResult(slideCaptcha, allow);
        captchaResult.setIncorrect(incorrect);
        return captchaResult;
    }

    @Override
    public CaptchaResult getClickSelectCaptcha(String key) {
        Map<Object, Object> map = this.redisTemplate.opsForHash().entries(key);
        String captcha = map.get("captcha").toString();
        int allow = Integer.parseInt(map.get("allow").toString());
        int incorrect = Integer.parseInt(map.get("incorrect").toString());
        ClickCaptcha clickCaptcha = JsonKit.toEntity(captcha, ClickCaptcha.class);
        CaptchaResult captchaResult = new CaptchaResult(clickCaptcha, allow);
        captchaResult.setIncorrect(incorrect);
        return captchaResult;
    }

    @Override
    public void removeCaptcha(String key) {
        this.redisTemplate.delete(key);
    }
}
